#!/usr/bin/perl

use IO::Socket;
use Tie::File;
use POSIX;

# Get settings
require('settings.cfg');

# Initiate variables for this scope
my %players, %playersxp, %playerslevel, %playersitems, %playersitemsum;
my $lastupdate, $thisupdate, $updatediff, $nick_tried = false, $lastsave, $thissave;
my @itemslot = ('weapon','armor','shield','helmet','gloves','boots','ring','amulet');

# Get player info
tie @playerinfo, 'Tie::File', 'players.info' or die 'Could not tie players.info';
foreach (@playerinfo) {
	m/([^,]+),(\d+,\d*),(\d*(?:,\d*){7})$/; # $1: Nick, $2: (XP,Level), $3: (weapon,armor,shield,helmet,gloves,boots,ring,amulet)
	( $players{$1}, $playersxp{$1}, $playerslevel{$1}, $playersitems{$1} ) = ( 0, ( split ',', $2 ), $3 );
	( $playeritemsum{$1} += $_ ) for split ',', $3;
	print "$1: $playerslevel{$1}-$playersxp{$1}, $playersitems{$1} ($playeritemsum{$1})\n";
}


# Get level-up levels
open( XPREQ, "< xpreq.dat") || die 'Could not open xpreq.dat';
my @xpreq = <XPREQ>;
close( XPREQ );

# Connect to IRC server
my $sock = new IO::Socket::INET(
	PeerAddr => $server,
	PeerPort => 6667,
	Proto => 'tcp'
) or die "Could not connect\n";

print $sock "NICK $nick\r\n"; # Change nickname
print $sock "USER $login 8 * :$login\r\n"; # Log in

# Get input while connecting
while (my $input = <$sock>) {
	print $input;
	if ($input =~ /004/) {
		# Connection successful
		last;
	}
	elsif ($input =~ /433/) {
		# Nickname is already in use
		if ( $nick_tried == false ) {
			print $sock "NICK $nick2\r\n"; # Try the second nickname
			$nick_tried = true;
			$nick = $nick2;
		} else {
			die "Nicknames are already in use.";
		}
	}
	elsif ($input =~ /PING :(.*)$/) {
		# Ping received
		print $sock "PONG $1\r\n";
	}
}

# Join channel
print $sock "JOIN $chan\r\n";

# Set variables
$lastupdate = time;
$lastsave = $lastupdate;

# Get all input
while (my $input = <$sock>) {

	print $input;

	# Check whether to update or not
	$thisupdate = time;	# This update
	if ( $thisupdate > $lastupdate + $time_between_updates ) {
		# Update
		while ( my ($key, $value) = each(%players) ) {
			if ( $value == 1 && $key ne $nick ) {
				# Update XP
				$playersxp{$key} += $thisupdate - $lastupdate;
				# Check level-up
				if ( $playersxp{$key} > xp_from_level( $playerslevel{$key} ) ) {

					# Level-up

					# Formal stuff
					$playerslevel{$key}++;
					if ( $chan_on_levelup == 1 ) {
						print_to_channel( "$key reached level " . $playerslevel{$key} . "! " . ceil( ( xp_from_level($playerslevel{$key}) - xp_from_level($playerslevel{$key}-1) ) / 60 ) . " min to next level." );
					} else {
						notice_user( $key, "You reached level " . $playerslevel{$key} . "! " . ceil( ( xp_from_level($playerslevel{$key}) - xp_from_level($playerslevel{$key}-1) ) / 60 ) . " min to next level." );
					}


					# Randomize item
					$item = int(rand($playerslevel{$key}*1.5))+1;
					$itemslot = int(rand(8));
					@items = split ',', $playersitems{$key};
					if ( $item > $items[$itemslot] ) {
						# Found superior item
						notice_user( $key, "You found a level $item " . $itemslot[$itemslot] . ". You toss away your old " . $items[$itemslot] . " " . $itemslot[$itemslot] . "." );
						$playersitemsum{$key} += $item - $items[$itemslot];
						$items[$itemslot] = $item;
						($items[0],$items[1],$items[2],$items[3],$items[4],$items[5],$items[6],$items[7]) = ($items[0],$items[1],$items[2],$items[3],$items[4],$items[5],$items[6],$items[7]);
						$playersitems{$key} = join ',', @items;
					} else {
						notice_user( $key, "You found a level $item " . $itemslot[$itemslot] . ". Unfortunately, you already have a " . $items[$itemslot] . " " . $itemslot[$itemslot] . "." );
						# Found inferior item
					}

					# Battle?
					if ( $playerslevel{$key} >= 25 || rand(4) > 3 ) {
						# Start battle
						$enemy = random_online_player();
						($victory,$p0r,$p1r,$p0m,$p1m) = player_battle( $key, $enemy );
						if ( $victory ) {
							# Victory
							$xp = int( ( ( $playerslevel{$enemy} > 28 ? $playerslevel{$enemy}/4 : 7 ) / 100 ) * ( $xpreq[$playerslevel{$key}] - $playersxp{$key} ) );
							print_to_channel( "$key [$p0r/$p0m] defeated $enemy [$p1r/$p1m]. $key gained $xp XP.");
							print $playersxp{$key};
							$playersxp{$key} += $xp;
							print $playersxp{$key} . "\n";
						} else {
							# Defeat
							$xp = int( 0.07 * ( $xpreq[$playerslevel{$key}] - $playersxp{$key} ) );
							print_to_channel( "$key [$p0r/$p0m] was defeated by $enemy [$p1r/$p1m]. $key lost $xp XP.");
							print $playersxp{$key};
							$playersxp{$key} -= $xp;
							print $playersxp{$key} . "\n";
						}
					}

				}
			}
		}
		$lastupdate = $thisupdate;
	}
	
	# Check whether to save or not
	$thissave = time; # This save
	if ( $thissave > $lastsave + $time_between_saves ) {
		# Save
		save_players();
	}

	# Process input
	chop $input;	# Remove linebreak
	if ($input =~ /^PING(.*)$/i) {
		# Ping received
		print $sock "PONG $1\r\n";	# Reply to ping
		print "PING\n";	# Output ping-message
	} elsif ( $input =~ /^:.+? 353 .+? = .+? :(.+)$/ ) {
		# User list received
		# $1 -- Space-separated list of players
		foreach (split ' ', $1 ) {	# For each nickname in the list
			s/[\+\@]//;	# Remove voice/op sign
			$players{$_} = 1;	# Set the player as online
		}
	} elsif ( $input =~ /^:(.+?)\!([^ ]+) JOIN/ ) {
		# User joined
		# $1 -- User's nickname
		# $2 -- User's hostmask
		$players{$1} = 1;	# Set player as online
	} elsif ( $input =~ /^:(.+?)\!([^ ]+) (PART|QUIT)/ ) {
		# User part/quit
		# $1 -- User's nickname
		# $2 -- User's hostmask
		$players{$1} = 0;	# Set player as offline
	} elsif ( $input =~ /^:(.+?)\!([^ ]+) KICK \#([^ ]+) (.*?) :(.*?)/ ) {
		# User kicked
		# $1 -- Op's nickname
		# $2 -- Op's hostname
		# $3 -- Channel kicked from
		# $4 -- Kicked's nickname
		# $5 -- Kick-reason
		$players{$4} = 0;
	} elsif ( $input =~ /^:(.+?)\!([^ ]+) NICK :([a-zA-Z0-9_-]*)/ ) {
		# User nick change
		# $1 -- User's former nickname
		# $2 -- User's hostmask
		# $3 -- User's new nickname
		$players{$1} = 0;	# Set former nick as offline
		$players{$3} = 1;	# Set new nick as online
	} elsif ( $input =~ /^:(.+?)\!([^ ]+) PRIVMSG \#([^ ]+) :(.*)/ ) {
		# Message to channel
		# $1 -- User's nickname
		# $2 -- User's hostmask
		# $3 -- Channel name
		# $4 -- Message
		if ( $4 =~ /^\!debug-players/ ) {
			# Debug: Show everyone's cumulated experience
			@pl = ();
			while ( my ($playername, $online) = each(%players) ) {
				push @pl, ( $online ? '+' : ' ' ) . $playername . ':' . $playerslevel{$playername} . '-' . $playersxp{$playername};
			}
			print_to_channel( join( ' | ', @pl ) );
		}
	} elsif ( $input =~ /^:(.+?)\!([^ ]+) PRIVMSG ([^ ]+) :([^ ]+) (.*)/ ) {
		# Message to bot
		# $1 -- User's nickname
		# $2 -- User's hostmask
		# $3 -- Bot's nick
		# $4 -- Password
		# $5 -- Rest of message
		if ( $4 eq $pass ) {
			# Access allowed
			$cmder = $1;
			$cmd = $5;
			if ( $cmd =~ /^save/ ) {
				# Save players.info
				save_players();
				print_to_channel( "Player info has been saved. ($cmder)" );
			} elsif ( $cmd =~ /^(quit|die)/ ) {
				# Quit
				save_players();
				print_to_channel( "Bye. ($cmder)" );
				die 'Terminated on command';
			} elsif ( $cmd =~ /^mass-online/ ) {
				# Mass-Online
				while ( my ($pn, $o) = each(%players) ) {
					$players{$pn} = 1;
				}
				print_to_channel( "All players set to online. ($cmder)" );
			}				
		} else {
			# Parse as user command
			$cmder = $1;
			$cmd = $4;
			$args = $5;
		}
	}
}

sub print_to_channel {
	$msg = $_[0];
	print $sock "PRIVMSG $chan :$msg\r\n";
}

sub notice_user {
	my ( $user, $msg ) = @_;
	print $sock "NOTICE $user :$msg\r\n";
}

sub xp_from_level {
	return $xpreq[$_[0]];
}

sub save_players {
	my $i = 0;
	while ( my ($playername, $online) = each(%players) ) {
		$playerinfo[$i] = join( ',', ($playername,$playersxp{$playername},$playerslevel{$playername},$playersitems{$playername}) );
		$i++;
	}
	return 1;
}

sub random_online_player {
	my @p = ();
	while ( my ($pn, $o) = each(%players) ) {
		if ( $o == 1 && $pn ne $nick ) {
			push @p, $pn;
		}
	}
	$r = rand(@p);
	return $p[$r];
}

sub player_battle {
	# Returns victory, p0r, p1r, p0m, p1m
	my ($p0,$p1) = @_;
	my ($p0m,$p1m) = ( $playersitemsum{$p0} + 1, $playersitemsum{$p1} + 1 );
	my ($p0r,$p1r) = ( int(rand($p0m)), int(rand($p1m)) );
	if ( $p0r > $p1r ) {
		# Attack succeeds
		return (1,$p0r,$p1r,$p0m,$p1m);
	} else {
		# Attack fails
		return (0,$p0r,$p1r,$p0m,$p1m);
	}
}
